//
//  TransmittingNode.cpp
//  Simulation
//
//  Created by Christopher Kemsley on 3/3/12.
//  Copyright (c) 2012 iAppolopolis. All rights reserved.
//

#include "EveryModel.hpp"

#include "EventMonitor.hpp"
#include "ModuleFactory.hpp"
#include "SimulationParameters.hpp"
#include "BroadcastData.hpp"
#include "Log.hpp"
#include <stdlib.h>
#include <time.h>
#include "TransmissionBroker.hpp"

using namespace Sim ;



struct	TransmittingModel_CSMA_CASettings : public EveryModelSettings
{
	unsigned	lambdaScalar ;
	std::string	target ;
	
	
	TransmittingModel_CSMA_CASettings	( std::string const & name )
	:	EveryModelSettings(name)
	,	lambdaScalar ( 1 )
		{
		add ( "lambdaScalar" , lambdaScalar ) ;
		add ( "target" , target ) ;
		}
} ;

struct	TransmittingModel_CSMA_CA : public EveryModel<TransmittingModel_CSMA_CASettings> , public ReceiverDelegate , public TransmitterDelegate
{
	TransmittingModel_CSMA_CA	( ModelVariant const & identity )
	:	EveryModel<TransmittingModel_CSMA_CASettings>	( identity )
	,	mProfile		( mConfig.location , mConfig.txRange )
	,	mReceiver		( mConfig.identifier[0] , mProfile , *this )
	,	mTransmitter	( mConfig.identifier[0] , *this )
		{
		setState(idle);
		generateTraffic();
		}
	
	virtual ~TransmittingModel_CSMA_CA ()
		{
		}
	
	DP<Timer>			stateTimer;
	TransmitterProfile	mProfile ;
	Receiver			mReceiver ;
	Transmitter			mTransmitter ;
	
	enum States{
		idle,				//Transmitter waiting for for packet to be sent from upper layers
		checkMed,			//If transmitter receives packet to be sent it will check medium
		waitToSend,			//Waiting for medium to become idle to send
		waitDIFS,			//DIFS period
		waitPauseDIFS,		//Pauses if transmission occurs during DIFS period while waiting to send
		sendData,			//
		packetCollision,
		backoff,			//Backoff period
	};
	
	States currentState;
		
	void	setState	(States newState)
	{	int x = newState;
		char states[8][20] ={"idle","checkMed","waitToSend","waitDIFS","waitPauseDIFS","sendData","packetCollision","backoff"};
		Log::notice("TransmittingModel_CSMA_CA") << "Changing State to: "<< states[x] << "." ;
		currentState = newState;
	}
	
	virtual void	receiveDidComplete	( Message * message )
		{
		BroadcastInfo::Type		messageType ;
		DeviceID				sender ;
		DeviceID				target ;
		
		decodeBroadcast ( message->bytes , messageType , sender , target ) ;
		
		if ( target == mConfig.identifier[0] )
			{//Only valid message should be ACK
				if(currentState == sendData){
				stateTimer<<0;
				Log::notice("TransmittingModel_CSMA_CA") << "Received ACK." ;
				generateTraffic();
				setState(idle);//change state
				getMultiplier(true);//reset contention window upon successful transmission
					;
				}
				else{
					//Should not get here
					Log::notice("TransmittingModel_CSMA_CA") << "Got valid message! (but we weren't supposed to!)" ;
				}
			}
		
		}

	virtual void	transmissionDidComplete ()
		{
			mReceiver.enable();
		}
	
	void	checkMedium(){
		if(mReceiver.isIdle() == true){//If medium is not busy, then transmit.
			sendTransmission();
		} else {//If not, transmtter waits until medium is not busy and waits to send.
			setState(waitToSend);
		}
	}
	
	void	generateTraffic(){//Creates traffic for the transmitter
		Log::notice("TransmittingModel_CSMA_CA") << "Generating Traffic" ;
		stateTimer << 0 ;
		Sim::Invocation*	x = ObjectInvocation<TransmittingModel_CSMA_CA>::create(this, &TransmittingModel_CSMA_CA::trafficReceived);
		stateTimer << new Timer(SimulationParameters::shared().randomBackoff(mConfig.lambdaScalar) , x);//Creates traffic for first packet and calls trafficReceived
	}
	
	void	trafficReceived(){//Trasmitter receives packets that need to be sent
		Log::notice("TransmittingModel_CSMA_CA") << "Traffic Received." ;
		setState(checkMed);
		checkMedium();
	}
	
	virtual void	mediumDidBecomeIdle ()
	{
		if(currentState == waitToSend){//If transmitter is waiting to send and medium becomes idle, then perform backoff.
			setState(backoff);
			randBackoff();
		}
		else if(currentState == waitPauseDIFS){//If transmitter timer currently paused because transmission occured, then unpause timer.
			setState(waitDIFS);
			stateTimer -> setIsPaused(0) ;//Unpause
		}
		else{
		}//Do nothing for other states.

	}
	
	virtual void	mediumDidBecomeBusy ()
	{
		if(currentState == waitDIFS){//If transmitter is waiting during DIFS and medium becomes busy, then pause timer.
			setState(waitPauseDIFS);
			stateTimer -> setIsPaused(1) ;//Pause Timer
		}
	}
	
	void randBackoff(){//Random backoff timer [1,CW0]
		stateTimer << 0 ;
		setState(backoff);
		Sim::Invocation*	x = ObjectInvocation<TransmittingModel_CSMA_CA>::create(this, &TransmittingModel_CSMA_CA::waitDifs);
		stateTimer << new Timer(SimulationParameters::shared().slotDuration * (1 + rand()%(SimulationParameters::shared().CW0*getMultiplier(true))) , x);
	}
	
	void waitDifs(){//Timer set for DIFS period 
		stateTimer << 0 ;
		setState(waitDIFS);
		Sim::Invocation*	x = ObjectInvocation<TransmittingModel_CSMA_CA>::create(this, &TransmittingModel_CSMA_CA::sendTransmission);
		stateTimer << new Timer(SimulationParameters::shared().DIFSDuration , x);
	}
	
	void sendTransmission(){//Data block encoded and sent
		Log::states("ListeningModel_CSMA_CA") << mConfig.identifier << ": Sending Data" ;
		stateTimer << 0 ;
		setState(sendData);
		Length	length	=	SimulationParameters::shared().controlPacketSize ;
		Byte	buffer		[length] ;
		encodeBlock ( buffer , length , mConfig.identifier[0] , mConfig.target[0] ) ;
		mTransmitter . transmit ( buffer,length ,  mConfig.target[0] , mProfile ) ;
		Invocation * i	=	ObjectInvocation<TransmittingModel_CSMA_CA> :: invocation ( this , &TransmittingModel_CSMA_CA:: collisionOccured) ; //If ack not received, collision ocurred.
		Interval	timeOutDelay	=	SimulationParameters::shared().activityDuration(SimulationParameters::shared().dataPacketSize) + 
										SimulationParameters::shared().activityDuration(SimulationParameters::shared().controlPacketSize) +
										SimulationParameters::shared().SIFSDuration;
		stateTimer	<<	new Timer (timeOutDelay , i ) ;//Interval based on how long it should take to transmit + SIFS + How long it takes for ack to be sent.
	}
	
	void collisionOccured(){
		if(currentState == sendData){// If timeout occurs during send data, then collision.
			setState(packetCollision);
			Log::notice("TransmittingModel_CSMA_CA") << "COLLISION!" ;
			getMultiplier(false);//Collision of packets occured
			randBackoff();
		}
	}
	
	int getMultiplier(bool selection){//Holds multiplier value for any collisions that occur and doubles contention window if one occurs.
		static int	multiplier = 1;
		if(selection == false){
			if(multiplier < 10){//If collision occurs double contention window
				Log::notice("TransmittingModel_CSMA_CA") << "Doubling Contention Window." ;
				multiplier++;
			}
		}
		else{
			multiplier = 1;
		}
		return multiplier;	
	}
	
} ;

//cs536-wireless-3.pdf reference



TypedModuleMaker<TransmittingModel_CSMA_CA> _TransmittingModel_CSMA_CA ( "csma_ca.active" ) ;
